{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "\n",
    "\n",
    "### <center> Автор материала: Семеновых Владимир (@MdScntst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center> Индивидуальный проект по анализу данных </center>\n",
    "## <center> Предсказание вероятности отклика абонентов оператора сотовой связи на предложенную услугу"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задача\n",
    "\n",
    "Предсказание вероятности отклика абонентов на предложенную услугу (в течении 2 недель после рассылки).\n",
    "\n",
    "Это реальные данные одной из телекоммуникационнах компаний, в целях соблюдения конфиденциальности данные были обезличены, колонки зашифрованы, значения линейно преобразованныб даты изменены.\n",
    "\n",
    "Ценность данной задачи в повышении прибыли от пользования абонентами новой услугой, а также минимизации расходов на рассылку предложения. Ну и в рамках данного курса желающие могут ознакомиться со спецификой данных телекома."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сбор данных\n",
    "\n",
    "О процессе сбора данных известно, что это агрегированные данные об использовании абонентом услуг связи за 2 месяца до рассылки оповещения о возможности подключить услугу. Рассылка происходила 10.10.2016.\n",
    "\n",
    "Данные [тут](https://yadi.sk/d/gLS1XmJ_3Ucqec)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Описание признаков\n",
    "\n",
    "Имеются 22 столбца: Var1 - Var22, при чем:\n",
    " - Var22 - целевой бинарный (1 - услуга подключена, 0 - нет)\n",
    " - Var21 - категориальный признак, в нем зашифрован тарифный план\n",
    " - Var19 - временной признак, обозначающий время последнего звонка\n",
    " - Var20 - временной признак, обозначающий время последнего платежа\n",
    " - Var1 - Var18 - полностью анонимные признаки, которые содержат в себе информацию о пользовании услугами и списаниях. А также пропущенные значения в данных означают отсутствие пользованием услуг и/или списаний"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2-3. Первичный и визуальный анализ данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Очен полезная либа для первичного анализа данных\n",
    "import pandas_profiling\n",
    "import pandas as pd\n",
    "pd.set_option(\"display.max_columns\", 22)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('Data.csv', parse_dates=[18,19], dayfirst=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pandas_profiling.ProfileReport(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Статистическая информация о переменных со значениями таргета 0 и 1 соответственно"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[data['Var22']==0].describe()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[data['Var22']==1].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из таблиц видно, то имеются значимые различия в переменных у абонентов откликнувшихся на предложение и нет. Посмотрим на графическое представление данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.rcParams['figure.figsize'] = 16,8\n",
    "plt.subplot(121)\n",
    "plt.hist(data['Var21'][data['Var22']==0], bins=40, color='red');\n",
    "plt.title('Распределение Тарифов для Target 0')\n",
    "plt.xlabel('Тариф')\n",
    "plt.ylabel('Количество')\n",
    "plt.subplot(122)\n",
    "plt.hist(data['Var21'][data['Var22']==1], bins=40,  color='green');\n",
    "plt.title('Распределение Тарифов для Target 1')\n",
    "plt.xlabel('Тариф')\n",
    "plt.ylabel('Количество');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Распределение тарифных планов доволно схоже, но отчетливо видны тарифные планы, абоненты которых более склонны к подключению услуги"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Распределения величин и выбросов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "columns = ['Var1', 'Var2', 'Var3', 'Var4', 'Var5', 'Var6', 'Var7', 'Var8', 'Var9',\n",
    "       'Var10', 'Var11', 'Var12', 'Var13', 'Var14', 'Var15', 'Var16', 'Var17',\n",
    "       'Var18']\n",
    "\n",
    "fig, axes = plt.subplots(nrows=9, ncols=2, figsize=(18, 50))\n",
    "for i, column in enumerate(columns): \n",
    "    sns.violinplot(y=data[column],x=data['Var22'].map({0:'Target 0', 1:'Target 1'}), data=data,split=True,inner=\"quart\", ax=axes[i // 2][i % 2], label='Target 0')\n",
    "    plt.xlabel('Target Value')\n",
    "    ax1.set_title(column)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут видно, что распределения Var1, 8, 12, 14, 16, 18, относящихся к разным классам довольно хорошо различаются как по распределению, так и по характеру выбросов"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на них подробнее"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "columns = ['Var1', 'Var2', 'Var3', 'Var4', 'Var5', 'Var6', 'Var7', 'Var8', 'Var9',\n",
    "       'Var10', 'Var11', 'Var12', 'Var13', 'Var14', 'Var15', 'Var16', 'Var17',\n",
    "       'Var18']\n",
    "sns.pairplot(data=data.dropna(), hue='Var22', vars=['Var1', 'Var8', 'Var12', 'Var14', 'Var16', 'Var18']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " Здесь можно отчетливо заметить, что в графиках зависимостей переменных Var12, Var14, Var18 от Var1; Var8 от Var12; Var12 от Var14 можно ярко выроженны зоны присутствия абонентов, откликнувшихся на предложение "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " #### Итого\n",
    " - Набор данных имеет 139448 записей и 22 переменных,\n",
    "\n",
    " - Var1 имеет 59.9% (83597) пропущенных значений, распределение сильно смещено вправо\n",
    " - Var2 имеет 59.9% (83597) пропущенных значений, распределение сильно смещено вправо\n",
    " - Var3 очень сильно коррелирует с Var2 (0.99627), и скорее всего представляет собой округленные значения Var2, поэтому эту переменную следует исключить\n",
    " - Var4 имеет 59.9% (83597) пропущенных значений, и 2.8% (3842) нулевых\n",
    " - Var5 имеет 7.2% (10069) пропущенных значений, распределение сильно смещено вправо\n",
    " - Var6 имеет 7.2% (10069) пропущенных значений\n",
    " - Var7 имеет 7.2% (10069) пропущенных значений, очень сильно коррелирует с Var6 (0.98806), его следует отбросить\n",
    " - Var8 имеет 7.2% (10069) пропущенных значений, и 4.4% (6159) нулевых и распределение сильно смещено вправо\n",
    " - Var9 имеет 5.4% (7568) пропущенных значений\n",
    " - Var10 имеет 5.4% (7568) пропущенных значений\n",
    " - Var11 имеет 46.8% (65294) пропущенных значений\n",
    " - Var12 имеет 46.8% (65294) пропущенных значений, и 12.4% (17353) нулевых и распределение сильно смещено вправо\n",
    " - Var13 имеет 89.3% (124519) пропущенных значений\n",
    " - Var14 имеет 89.3% (124519) пропущенных значений, и 1.4% (1942) нулевых и распределение сильно смещено вправо\n",
    " - Var15 имеет 79.4% (110788) пропущенных значений, и 1.8% (2574) нулевых и распределение сильно смещено вправо\n",
    " - Var16 имеет 79.4% (110788) пропущенных значений, и 12.2% (17071) нулевых и распределение сильно смещено вправо\n",
    " - Var17 имеет 32.7% (45564) пропущенных значений\n",
    " - Var18 имеет 32.7% (45564) пропущенных значений, распределение сильно смещено вправо\n",
    " - Var19 имеет 34.5% (48152) пропущенных значений\n",
    " - Var20 имеет 11% (15283) пропущенных значений\n",
    " - Var22 в целевой переменной положительный класс сотавляет около 0.7%\n",
    " \n",
    "Var1, 8, 12, 14, 16, 18 - переменные хорошо характеризуют абонентов, относящихся к разным классам, но ввиду их анонимности, мы не можем охарактеризовать их значения."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Инсайты\n",
    "\n",
    " - Распределение всех переменных сильно смещено вправо, это обхясняется динамикой пользования услугами связи: большинство пользователей в среднем тратят **n** количество денег в месяц на связь, выговаривают **m**, минут и т.д., а как показывают скрипичные диаграммы нередки случаи отклонений от общего среднего. Так как предоставленные данные получены из автоматической биллинговой системы, это практически полностью исключает возможность неверной записи данных, следовательно выбросы с высокой долей вероятности можно отнести к экстремальным значениям переменных. \n",
    " - Пропущенные значения, как указано в разделе, описания данных, являются фактом отсутствия пользования услугой и/или взимания средств за использование услуг связи, следовательно эти значения должны быть заменены на 0.\n",
    " - Из данных видно, что отсутствуют социально-демографические данные (возраст,пол...)\n",
    " - Сильно коррелируют между собой: Var1-4, Var5-7, Var9-10, Var13-14, скорее всего корреляция возникает между использованными услугами и выставленным счетом за \n",
    " - 4400 строк идентичны\n",
    " - Переменные Var13-16 следует удалить из выборки ввиду подавляющего числа пропущенных значений\n",
    " - Анализируя скрипичные диаграммы и графики плотностей распределения, и статичтическую информацию можно сделать вывод, что классы хорошо разделимы, но ввиду большой разбалансировки целевой переменной, модели будут учиться предсказывать мажорный класс"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Выбор метрики\n",
    "\n",
    "\n",
    "В данных наблидается значительный дисбаланс классов. Самые распространённые метрики для бинарной классификации с дисбалансом данных -  roc-auc, precision, recall и а1-score, так как нас интересует именно вероятность отклика и, то есть границу принятия решения можно двигать и классификация бинарная - то наиболее полезной будет **roc-auc**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Выбор модели\n",
    "\n",
    "Существует множество методов бинарной классификации, в данном случае я буду использовать два подхода - линейный и \"деревянный\":\n",
    " - LogisticRegression - логистическая регрессия является классической моделью для выявления зависимостей. В данном случае нужно применять one hot encoding для категориальных переменных и нормализовывать вещественные. К тому же т.к. в данных присутствуют выбросы и они являются экстемальными значениями, которые тоже являются полезной информацией ввиду своей природы (да и учитывая их количество), линейная модель будет давать большую ошибку, но будет являться отличным бэйзлайном.\n",
    " - XGBoost и RandomForestClassifier - для задачи с присутствием категориальных переменных, \"разношерстных\" вещественных, да еще и с большим числом пропусков и/или нулевых значений, данные методы самое то, т.к. основаны на деревьях решений!)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Предобработка данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import StratifiedKFold, cross_val_score, GridSearchCV, train_test_split, learning_curve,validation_curve\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from xgboost import XGBClassifier\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.preprocessing import StandardScaler, normalize\n",
    "from sklearn.metrics import roc_auc_score\n",
    "import numpy as np\n",
    "from scipy.stats import boxcox\n",
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Так ка было решено использовать линейные модели и модели, основанные на решающих деревьях, данные бедут проходить разную предобработку"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg = data.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree = data.copy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Сначала проведем предобработку данных для моделей основанных на решающих деревьях."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделим целевую переменную"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_tree = data_tree.Var22.values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из данных удалим временные переменные и целевое поле"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree.drop(['Var19', 'Var20', 'Var22'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заполним пропущенные значения нулями, т.к. эти значения показывают отсутствие пользованиея услугой"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree.fillna(0, inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ПРоизведем разбивку данных на обучающую и валидационную с учетом баланса классов"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_tree_train, X_tree_val, y_tree_train, y_tree_val = train_test_split(data_tree.values, y_tree, test_size=0.3,\n",
    "                                                                     random_state=42, shuffle=True, stratify=y_tree)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тут собственно все!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Предобработка для линейной модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сначала повторим преобразования предыдущего шага"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_reg = data_reg.Var22.values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg.drop(['Var19', 'Var20', 'Var22'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg.fillna(0, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg = pd.get_dummies(data_reg, prefix_sep='_', columns=['Var21'], sparse=False, drop_first=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавим OHE признаки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_reg_train, X_reg_val, y_reg_train, y_reg_val = train_test_split(data_reg.values, y_reg, test_size=0.3,\n",
    "                                                                     random_state=42, shuffle=True, stratify=y_reg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все переменные были с сильным скосом распределения вправо, поэтому прогогарифмируем их (кроме OHE признаков)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_reg_train[:, :18] = np.log1p(X_reg_train[:, :18])\n",
    "X_reg_val[:, :18] = np.log1p(X_reg_val[:, :18])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Отмасштабируем данные"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler = StandardScaler()\n",
    "X_reg_train[:, :18] = scaler.fit_transform(X_reg_train[:, :18])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_reg_val[:, :18] = scaler.transform(X_reg_val[:, :18])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(X_reg_val[:, 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже чуточку лучше"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Кросс-валидация и настройка гиперпараметров модели"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Добавим разбиение выборки с учетом балатса классов на 5 фолдов для кросс-валидации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 8.1. Сначала линейная модель"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = LogisticRegression(random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим словарь с основными параметрами LogisticRegression и найдем лучшие параметры\n",
    "\n",
    "Тут:\n",
    " - **С** - обратное значение коэффициента регуляризации (силы регуляризации)\n",
    " - **class_weight** - используем ли веса для предикторов раззных классов в соответствии с их соотношением\n",
    " - **solver** - алгоритм оптимизации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'C': [0.01, 0.05, 0.1, 1, 5, 10, 20, 50, 100],\n",
    "    'class_weight':[None, 'balanced'], \n",
    "    'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],\n",
    "    \n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(model, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_reg_train, y_reg_train);\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = LogisticRegression(C= 0.01, class_weight= 'balanced', solver= 'liblinear')\n",
    "model.fit(X_reg_train, y_reg_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_reg_val, model.predict_proba(X_reg_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 8.2. Случайный лес"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Основные параметры:\n",
    "\n",
    " - **n_estimators** - число деревьев\n",
    " - **criterion** - пригцип деления дерева на листья\n",
    " - **max_features** - максимальное число используемых признаков\n",
    " - **max_depth** - максимальная глубина дерева\n",
    " - **min_samples_leaf** - минимальное число объектов, допустимое в листовом узле"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = RandomForestClassifier()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'max_depth': [10,  20, 30], \n",
    "    'n_estimators': [400,500], \n",
    "    'max_features': [1, 'auto'], \n",
    "    'criterion': ['gini', 'entropy'], \n",
    "    'min_samples_leaf': [0.1, 0.5, 1]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(forest, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf, verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_tree_train, y_tree_train)\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = RandomForestClassifier(criterion='gini', max_depth=10, max_features=1, min_samples_leaf= 1, n_estimators= 400)\n",
    "model.fit(X_tree_train, y_tree_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_tree_val, model.predict_proba(X_tree_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 8.3. XGBoost"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Основные параметры:\n",
    "\n",
    " - **max_depth** - максимальная глубина дерева\n",
    " - **n_estimators** - пчисло деревьев\n",
    " - **learning_rate** - learning_rate\n",
    " - **reg_lambda** - сила L2 регуляризации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = XGBClassifier(random_state=17, n_jobs=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'max_depth': [3,4,5,6], \n",
    "    'n_estimators': [60,80, 100,200, 300], \n",
    "    'learning_rate':[0.01,0.05,0.1,0.5], \n",
    "    'reg_alpha': [0.1, 0.2,0.3]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(forest, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf, verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_tree_train, y_tree_train)\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = XGBClassifier(max_depth=3,  n_estimators= 200,  learning_rate=0.1, reg_lambda=0.1)\n",
    "model.fit(X_tree_train, y_tree_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_tree_val, model.predict_proba(X_tree_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результаты показывают, что модели основанные на деревьях решений показывают схожий результат на тесте, а логистическая регрессия опережайт оба метода по результатам"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Создание новых признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данной задаче должны были зайти фичи, связанные с подключаемой услугой: цена за минуту внутри сети, между операторами, междугородные звонки, стоимость эдиницы трафика и т.д. Но вследствие того, что данные зашифрованы мы не можем вычислить данные параметры. Остается работать только с датами: сколько прошло времени с последнего звонка, пополнения счета, факт наличия звонка за последние 2 месяа, факт наличия пополнения за последние 2 месяца."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Факт совершения звонка за последние 2 мес\n",
    "data['is_call'] = 1\n",
    "data['is_call'][data['Var19'].isnull()] = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Факт совершения платежа за последние 2 мес\n",
    "data['is_topup'] = 1\n",
    "data['is_topup'][data['Var20'].isnull()] = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Заменаем пропуски в данных на минимальное значение даты\n",
    "data['Var19'].fillna(data['Var19'].min(), inplace=True)\n",
    "data['Var20'].fillna(data['Var20'].min(), inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Вводим дату рассылки\n",
    "data['message_date'] = '10-10-2016'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['message_date'] = pd.to_datetime(data['message_date'], format='%d-%m-%Y')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Число дней с момента последнего звонка и пополнения до даты рассылки соответственно\n",
    "data['calls_days_ago'] = (data['message_date'] -data['Var19']).dt.days\n",
    "data['topup_days_ago'] = (data['message_date'] -data['Var20']).dt.days"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Создание выборки для линейной модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg = data.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_reg = data_reg.Var22.values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg.drop(['Var19', 'Var20', 'Var22', 'message_date'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg.fillna(0, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_reg = pd.get_dummies(data_reg, prefix_sep='_', columns=['Var21'], sparse=False, drop_first=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# C учетом новых переменных\n",
    "X_reg_train, X_reg_val, y_reg_train, y_reg_val = train_test_split(data_reg.values, y_reg, test_size=0.3,\n",
    "                                                                     random_state=42, shuffle=True, stratify=y_reg)\n",
    "\n",
    "X_reg_train[:, :22] = np.log1p(X_reg_train[:, :22])\n",
    "X_reg_val[:, :22] = np.log1p(X_reg_val[:, :22])\n",
    "\n",
    "scaler = StandardScaler()\n",
    "X_reg_train[:, :22] = scaler.fit_transform(X_reg_train[:, :22])\n",
    "\n",
    "X_reg_val[:, :22] = scaler.transform(X_reg_val[:, :22])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = LogisticRegression(random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'C': [0.01, 0.05, 0.1, 1, 5, 10, 20, 50, 100],\n",
    "    'class_weight':[None, 'balanced'], \n",
    "    'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],\n",
    "    \n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(model, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_reg_train, y_reg_train);\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = LogisticRegression(C= 0.01, class_weight= 'balanced', solver= 'liblinear')\n",
    "model.fit(X_reg_train, y_reg_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_reg_val, model.predict_proba(X_reg_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результат чуть улучшился. Посмотрим на случайный лес"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Создание выборки для деревьев"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree = data.copy()\n",
    "y_tree = data_tree.Var22.values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree.drop(['Var19', 'Var20', 'Var22','message_date'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tree.fillna(0, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_tree_train, X_tree_val, y_tree_train, y_tree_val = train_test_split(data_tree.values, y_tree, test_size=0.3,\n",
    "                                                                     random_state=42, shuffle=True, stratify=y_tree)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Испытываем случайный лес"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = RandomForestClassifier(random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'max_depth': [10, 20, 30], \n",
    "    'n_estimators': [400],#500], \n",
    "    'max_features': [1], #'auto'], \n",
    "    'criterion': ['gini'], #'entropy'], \n",
    "    'min_samples_leaf': [1],# 0.5, 1]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(forest, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf, verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_tree_train, y_tree_train)\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = RandomForestClassifier(criterion='entropy', max_depth=10, max_features=1, min_samples_leaf= 1, n_estimators= 300)\n",
    "model.fit(X_tree_train, y_tree_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_tree_val, model.predict_proba(X_tree_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В случае леса скор не увеличивается"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Испытываем xgboost"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = XGBClassifier(random_state=17, n_jobs=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param_grid = {\n",
    "    'max_depth': [4], \n",
    "    'n_estimators': [100], \n",
    "    'learning_rate':[0.01,0.05,0.1,0.5], \n",
    "    'reg_alpha': [0.1, 0.2,0.3]\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs = GridSearchCV(forest, param_grid, scoring='roc_auc', n_jobs=-1, cv=skf, verbose=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gs.fit(X_tree_train, y_tree_train)\n",
    "print('Best parameters: ', gs.best_params_)\n",
    "print('Best score: ', gs.best_score_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = XGBClassifier(max_depth=4,  n_estimators= 100,  learning_rate=0.1, reg_lambda=0.1)\n",
    "model.fit(X_tree_train, y_tree_train)\n",
    "print('ROC-AUC on validation set: ', roc_auc_score(y_tree_val, model.predict_proba(X_tree_val)[:,1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В случае xgboost наблюдается увеличение качества модели, но все равно до логистической регресии не дотягивает"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. Кривые валидации и обучения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Кривые валидации буду строить на лучшей модели, которая показала себя на предыдущих этапах - **LogisticRegression**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,\n",
    "                        n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):\n",
    "    '''\n",
    "    http://scikit-learn.org/stable/auto_examples/model_selection/plot_learning_curve.html\n",
    "    '''\n",
    "    \n",
    "    plt.figure()\n",
    "    plt.title(title)\n",
    "    if ylim is not None:\n",
    "        plt.ylim(*ylim)\n",
    "    plt.xlabel(\"Training examples\")\n",
    "    plt.ylabel(\"Score\")\n",
    "    train_sizes, train_scores, test_scores = learning_curve(\n",
    "        estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, scoring='roc_auc')\n",
    "    train_scores_mean = np.mean(train_scores, axis=1)\n",
    "    train_scores_std = np.std(train_scores, axis=1)\n",
    "    test_scores_mean = np.mean(test_scores, axis=1)\n",
    "    test_scores_std = np.std(test_scores, axis=1)\n",
    "    plt.grid()\n",
    "\n",
    "    plt.fill_between(train_sizes, train_scores_mean - train_scores_std,\n",
    "                     train_scores_mean + train_scores_std, alpha=0.1,\n",
    "                     color=\"r\")\n",
    "    plt.fill_between(train_sizes, test_scores_mean - test_scores_std,\n",
    "                     test_scores_mean + test_scores_std, alpha=0.1, color=\"g\")\n",
    "    plt.plot(train_sizes, train_scores_mean, 'o-', color=\"r\",\n",
    "             label=\"Training score\")\n",
    "    plt.plot(train_sizes, test_scores_mean, 'o-', color=\"g\",\n",
    "             label=\"Cross-validation score\")\n",
    "\n",
    "    plt.legend(loc=\"best\")\n",
    "    return plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(6, 4))\n",
    "plot_learning_curve(model, 'Logistic Regression', X_reg_train, y_reg_train, cv=5, n_jobs=-1);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "График в целом адекватный, но видно, что имеестся стремление к недообучению. Использование фич, которые созданы на не зашифрованных данных могло бы поправить ситуацию"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#http://scikit-learn.org/stable/auto_examples/model_selection/plot_validation_curve.html\n",
    "plt.figure(figsize=(18, 10))\n",
    "param_range=np.array([0.005, 0.01, 0.05, 0.1, 0.5, 5])\n",
    "train_scores, test_scores = validation_curve(model, X_reg_train, y_reg_train, param_name=\"C\",\n",
    "                                             param_range=param_range, cv=5, scoring=\"roc_auc\", n_jobs=-1)\n",
    "train_scores_mean = np.mean(train_scores, axis=1)\n",
    "train_scores_std = np.std(train_scores, axis=1)\n",
    "test_scores_mean = np.mean(test_scores, axis=1)\n",
    "test_scores_std = np.std(test_scores, axis=1)\n",
    "\n",
    "plt.title(\"Validation Curve\")\n",
    "plt.xlabel(\"C\")\n",
    "plt.ylabel(\"Score\")\n",
    "#plt.ylim(0.0, 1.1)\n",
    "lw = 2\n",
    "plt.plot(param_range, train_scores_mean, label=\"Training score\",\n",
    "             color=\"darkorange\", lw=lw)\n",
    "plt.fill_between(param_range, train_scores_mean - train_scores_std,\n",
    "                 train_scores_mean + train_scores_std, alpha=0.2,\n",
    "                 color=\"darkorange\", lw=lw)\n",
    "plt.plot(param_range, test_scores_mean, label=\"Cross-validation score\",\n",
    "             color=\"navy\", lw=lw)\n",
    "plt.fill_between(param_range, test_scores_mean - test_scores_std,\n",
    "                 test_scores_mean + test_scores_std, alpha=0.2,\n",
    "                 color=\"navy\", lw=lw)\n",
    "plt.legend(loc=\"best\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При увеличении значения C качество модели немного ухудшается."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Прогноз для тестовой или отложенной выборки"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тестовая выборка формировалась из исходных данных посредством случайного выбора 30% данных из оригинальной выборки с сохранением пропорций целевой переменной. Прогнозы на тестовой выборке представленны в разделах 8 и 9 данного проекта"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. Выводы"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построена и протестирована модель отклика на предложение о подключении услуги, которая показала довольно неплохой резултат в условиях сильной разбалансировки значений целевой переменной. Ценность модели заключается в возможности её использования для предсказания откликов на маркетинговые активности, выбирать пул колентов с наибольшей вероятностью отклика.\n",
    "\n",
    "\n",
    "Пути улучшения результатов:\n",
    " - более точная настройка параметров модели\n",
    " - деанонимизация данных\n",
    " - использование нелинейных сочетаний признаков\n",
    " - небольшой бустинг числа минорного класса\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
